#!/usr/bin/env python3
import os
import re
import subprocess
from collections import defaultdict
from pathlib import Path
from typing import Dict, List, Set

import requests

GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
HEADERS = {"Authorization": f"token {GITHUB_TOKEN}"} if GITHUB_TOKEN else {}
GH_API = "https://api.github.com"

def fetch_all(url: str, params: Dict[str, str] | None = None) -> List[dict]:
    """Paginate until we exhaust the GitHub `Link: rel="next"` header."""
    params = dict(params or {}, per_page=100)
    items: List[dict] = []
    while url:
        r = requests.get(url, headers=HEADERS, params=params, timeout=30)
        r.raise_for_status()
        items.extend(r.json())
        # pagination
        link = r.headers.get("Link", "")
        nxt = re.search(r'<([^>]+)>;\s*rel="next"', link)
        url, params = (nxt.group(1), None) if nxt else ("", None)
    return items

def merged_pr_authors(owner_repo: str,
                      label: str | None = None) -> Set[str]:
    """Return the *login* of everyone whose PR is merged. If `label` is given then only looks for the label."""
    url = f"{GH_API}/repos/{owner_repo}/pulls"
    prs = fetch_all(url, {"state": "closed"})
    authors: Set[str] = set()
    for pr in prs:
        if label is None and pr.get("merged_at") is None:
            continue
        if label and all(l["name"] != label for l in pr.get("labels", [])):
            continue
        authors.add(pr["user"]["login"])
    return authors

def translation_contribs(repo_dir: Path) -> Dict[str, Set[str]]:
    """
    Extract mapping {name -> {languages}} from 'Co-authored-by' lines.
    Expected pattern:
        Co-authored-by: Alice Example <alice@dom.tld> (lt, ru)
    """
    cmd = ["git", "-C", str(repo_dir), "log", "--all", "--grep", "Co-authored-by:"]
    log_txt = subprocess.check_output(cmd, text=True, encoding="utf-8", errors="ignore")
    pattern = re.compile(
        r"Co-authored-by:\s*(?P<name>[^<]+?)\s*<(?P<email>[^>]+)>\s*\((?P<langs>[^)]+)\)",
        re.IGNORECASE,
    )
    contribs: Dict[str, Set[str]] = defaultdict(set)
    names = {}
    for line in reversed(log_txt.splitlines()):
        m = pattern.search(line)
        if not m:
            continue
        email = m.group("email").strip()
        name = m.group("name").strip()
        langs = [l.strip() for l in m.group("langs").split(",")]
        contribs[email].update(langs)
        names[email] = name

    blacklist = set(["admin@sapples.net", "alex@futo.org"])
    output = {}
    for k, v in contribs.items():
        if k not in blacklist:
            output[names[k]] = v
    return output

def to_kotlin_list(name: str, items: List[str]) -> str:
    items_str = ",\n    ".join(f"\"{i}\"" for i in items)
    return (
        f"@Suppress(\"HardCodedStringLiteral\")\n"
        f"val {name}: List<String> = listOf(\n"
        f"    {items_str}\n"
        f")"
    )

def main() -> None:
    # 1. languageContribs
    repo_root = Path(__file__).resolve().parent.parent

    # need to pull the private repo https://gitlab.futo.org/keyboard/keyboard-translations for email-based deduplication
    langs_map = translation_contribs(repo_root / "keyboard-translations")
    lang_lines = [
        f"{n} ({', '.join(sorted(langs))})" for n, langs in sorted(langs_map.items())
    ]

    # 2. layoutContribs
    layout_authors = sorted(merged_pr_authors("futo-org/futo-keyboard-layouts"))

    # 3. codeContribs
    code_authors = sorted(
        merged_pr_authors("futo-org/android-keyboard", label="PR Merged")
    )

    # 4. Output Kotlin
    print("// Generated by tools/contributors.py, please do not update manually.")
    print("package org.futo.inputmethod.latin.uix.settings.pages.credits")
    print("")
    print(to_kotlin_list("languageContribs", lang_lines))
    print("")
    print(to_kotlin_list("layoutContribs", layout_authors))
    print("")
    print(to_kotlin_list("codeContribs", code_authors))


if __name__ == "__main__":
    main()
